using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;

namespace mebc4.tablebased.asynchronous
{
    class MebcRuntime : IDisposable
    {
        private readonly Dictionary<string, List<string>> mappings = new Dictionary<string, List<string>>();
        private readonly Dictionary<string, Delegate> inputPins = new Dictionary<string, Delegate>();

        public void Map(string outputPinName, string inputPinName)
        {
            List<string> mapping;
            if (!mappings.TryGetValue(outputPinName, out mapping))
            {
                mapping = new List<string>();
                mappings.Add(outputPinName, mapping);
            }

            mapping.Add(inputPinName);
        }


        public void Reg<T>(string inputPinName, Action<T> processor)
        {
            inputPins.Add(inputPinName, processor);
        }


        private void Send(string outputPinName, object message)
        {
            Console.WriteLine("   send({0}, {1}), thread {2}", outputPinName, message!=null ? message.GetType().Name : "NULL", Thread.CurrentThread.GetHashCode());

            var mapping = mappings[outputPinName];
            mapping.ForEach(m =>
                                {
                                    var p = inputPins[m];
                                    p.DynamicInvoke(message);
                                });
        }


        private readonly ConcurrentQueue<Tuple<string, object>> messages = new ConcurrentQueue<Tuple<string, object>>();
        private readonly AutoResetEvent messagesWaiting = new AutoResetEvent(false);
        private bool processMessages = true;
        private Thread messageProcessing;

        public void Post(string outputPinName, object message)
        {
            messages.Enqueue(new Tuple<string, object>(outputPinName, message));
            messagesWaiting.Set();
        }


        public void Run()
        {
            messageProcessing = new Thread(() =>
                                               {
                                                   while (processMessages)
                                                   {
                                                       Tuple<string, object> message;
                                                       while (messages.TryDequeue(out message))
                                                       {
                                                           Send(message.Item1, message.Item2);
                                                       }
                                                       messagesWaiting.WaitOne();
                                                   }
                                               });
            messageProcessing.IsBackground = true;
            messageProcessing.Start();
        }


        public void Dispose()
        {
            processMessages = false;
            messagesWaiting.Set();
            messageProcessing.Join();
        }
    }
}